/*
 * Copyright (C) 2011, EADS France
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 */
package xowl.interpreter.backends.rete;

import java.util.Collection;
import java.util.Iterator;
import xowl.interpreter.backends.rdf.RDFNode;

/**
 *
 * @author Laurent WOUTERS
 */
public class BetaJoinNode implements FactActivable, TokenActivable {
    private FactHolder alphaMem;
    private TokenHolder betaMem;
    private BetaJoinNodeTest test1;
    private BetaJoinNodeTest test2;
    private BetaJoinNodeTest test3;
    private BetaMemory child;
    
    public BetaMemory getChild() { return child; }
    
    public BetaJoinNode(FactHolder alpha, TokenHolder beta, Collection<BetaJoinNodeTest> tests) {
        this.alphaMem = alpha;
        this.betaMem = beta;
        Iterator<BetaJoinNodeTest> iter = tests.iterator();
        if (iter.hasNext()) test1 = iter.next();
        if (iter.hasNext()) test2 = iter.next();
        if (iter.hasNext()) test3 = iter.next();
        this.alphaMem.addChild(this);
        this.betaMem.addChild(this);
    }
    
    public boolean match(FactHolder alpha, Collection<BetaJoinNodeTest> tests) {
        if (this.alphaMem != alpha)
            return false;
        if (this.test1 != null) {
            if (!tests.contains(this.test1))
                return false;
            else if (this.test2 != null) {
                if (!tests.contains(this.test2))
                    return false;
                else if (this.test3 != null)
                    return tests.contains(this.test3);
                return (tests.size() == 2);
            }
            return (tests.size() == 1);
        }
        return (tests.isEmpty());
    }
    
    @Override public void activateToken(Token t) {
        for (TripleFact fact : alphaMem.getFacts())
            if (passTests(t, fact))
                child.activate(t, fact);
    }
    @Override public void deactivateToken(Token t) {
        for (TripleFact fact : alphaMem.getFacts())
            if (passTests(t, fact))
                child.deactivate(t, fact);
    }
    @Override public void activateTokens(Collection<Token> tokens) {
        if (!alphaMem.getFacts().isEmpty())
            child.activate(getJoin(tokens, alphaMem.getFacts()));
    }
    @Override public void deactivateTokens(Collection<Token> tokens) {
        if (!alphaMem.getFacts().isEmpty())
            child.deactivate(getJoin(tokens, alphaMem.getFacts()));
    }
    
    @Override public void activateFact(TripleFact fact) {
        for (Token t : betaMem.getTokens())
            if (passTests(t, fact))
                child.activate(t, fact);
    }
    @Override public void deactivateFact(TripleFact fact) {
        for (Token t : betaMem.getTokens())
            if (passTests(t, fact))
                child.deactivate(t, fact);
    }
    @Override public void activateFacts(Collection<TripleFact> facts) {
        if (!betaMem.getTokens().isEmpty())
            child.activate(getJoin(betaMem.getTokens(), facts));
    }
    @Override public void deactivateFacts(Collection<TripleFact> facts) {
        if (!betaMem.getTokens().isEmpty())
            child.deactivate(getJoin(betaMem.getTokens(), facts));
    }
    
    private Iterator<Couple> getJoin(Collection<Token> tokens, Collection<TripleFact> facts) {
        int size = tokens.size() * facts.size();
        JoinStrategy join = null;
        if (size <= 10)
            join = new NestedLoopJoin(test1, test2, test3);
        else if (size <= 10000)
            join = createDoubeHashJoin();
        else if (facts.size() < tokens.size())
            join = createSimpleHashJoinFact();
        else
            join = createSimpleHashJoinToken();
        return join.join(tokens, facts);
    }
    
    private JoinStrategy createDoubeHashJoin() {
        return new GraceHashJoin<Token, TripleFact>(test1, test2, test3) {
            @Override protected <T1, T2> Couple createCouple(T1 t1, T2 t2) { return new Couple((TripleFact)t2, (Token)t1); }
            @Override protected <T1> RDFNode getValueForT1(T1 t1, BetaJoinNodeTest test) { return ((Token)t1).getBinding(test.getVariable()); }
            @Override protected <T2> RDFNode getValueForT2(T2 t2, BetaJoinNodeTest test) { return ((TripleFact)t2).getTriple().getField(test.getField()); }
            @Override public Iterator<Couple> join(Collection<Token> tokens, Collection<TripleFact> facts) { return joinGenerics(tokens, facts); }
        };
    }
    private JoinStrategy createSimpleHashJoinToken() {
        return new SimpleHashJoin<Token, TripleFact>(test1, test2, test3) {
            @Override protected <T1, T2> Couple createCouple(T1 t1, T2 t2) { return new Couple((TripleFact)t2, (Token)t1); }
            @Override protected <T1> RDFNode getValueForT1(T1 t1, BetaJoinNodeTest test) { return ((Token)t1).getBinding(test.getVariable()); }
            @Override protected <T2> RDFNode getValueForT2(T2 t2, BetaJoinNodeTest test) { return ((TripleFact)t2).getTriple().getField(test.getField()); }
            @Override public Iterator<Couple> join(Collection<Token> tokens, Collection<TripleFact> facts) { return joinGenerics(tokens, facts); }
        };
    }
    private JoinStrategy createSimpleHashJoinFact() {
        return new SimpleHashJoin<TripleFact, Token>(test1, test2, test3) {
            @Override protected <T1, T2> Couple createCouple(T1 t1, T2 t2) { return new Couple((TripleFact)t1, (Token)t2); }
            @Override protected <T1> RDFNode getValueForT1(T1 t1, BetaJoinNodeTest test) { return ((TripleFact)t1).getTriple().getField(test.getField()); }
            @Override protected <T2> RDFNode getValueForT2(T2 t2, BetaJoinNodeTest test) { return ((Token)t2).getBinding(test.getVariable()); }
            @Override public Iterator<Couple> join(Collection<Token> tokens, Collection<TripleFact> facts) { return joinGenerics(facts, tokens); }
        };
    }
    
    private boolean passTests(Token t, TripleFact fact) {
        if (test1 == null) return true;
        if (!test1.check(t, fact)) return false;
        if (test2 == null) return true;
        if (!test2.check(t, fact)) return false;
        if (test3 == null) return true;
        return test3.check(t, fact);
    }
    
    public BetaMemory resolveMemory(Collection<Binder> binders) {
        if (child != null) {
            child.addBinders(binders);
            return child;
        }
        child = new BetaMemory();
        child.addBinders(binders);
        if (!alphaMem.getFacts().isEmpty())
            activateFacts(alphaMem.getFacts());
        return child;
    }
}
